/*
GameServer.cpp
定义游戏服务端，负责与客户端通信
*/

#include <hv/WebSocketServer.h>
#include <chrono>
#include <queue>
#include <time.h>
#include <unordered_map>
#include <future>
#include <GameServer.h>

using namespace hv;
using namespace std;

GameServer::GameServer() : interactHandler(&(this->level)) {};

void GameServer::start(string ip, int port, int maxPlayer) {
    future<void> serverFuture = async(launch::async, [this, ip, port, maxPlayer](){ startServer(ip, port, maxPlayer); });
    future<void> hostFuture = async(launch::async, [this, ip, port, maxPlayer](){ startHostGame(ip, port, maxPlayer); });
    string command;
    level.serverName = serverName;
    level.serverInfo = serverInfo;
    level.mapHeight = mapHeight;
    level.mapWidth = mapWidth;
    level.players.reserve(maxPlayer);
    while (true) {
        getline(cin, command);
        if (command == "help") {
            // 以下是所有的服务端命令，详情见文档
            printf("=== Help Pages ===\n");
            printf("List Targets           -> ls <player | bullet | youkai | channel | item>\n");
            printf("Game Option            -> game <start | stop | status | rank>\n");
            printf("Boardcast              -> boardcast <content>\n");
            printf("Kick Player            -> kick <uuid>\n");
            printf("Stop Server            -> stop\n");
            printf("Copyleft/right related -> copyright | license\n");
            printf("999                    -> cirno\n");
            printf("=== Help Pages ===\n");
        }
        else if (command.length() >= 11 && command.substr(0, 9) == "boardcast") {
            string target = command.substr(10);
            sendGameLog(target, responseBuffer);
        }
        else if (command.length() >= 6 && command.substr(0, 4) == "kick") {
            string targetUuid = command.substr(5);
            bool kicked = false;
            for (int i = 0; i < clientChannelsUuid.size(); i++) {
                if (clientChannelsUuid[i].substr(1) == targetUuid) {
                    string targetName = "An observer";
                    if (level.players.find(targetUuid) != level.players.end()) {
                        targetName = level.players[targetUuid].playerName;
                    }
                    sendGameLog(targetName + " has been kicked from the server.", responseBuffer);
                    clientChannels[i]->close();
                    deleteDeadChannels();
                    kicked = true;
                    break;
                }
            }
            if (!kicked) { printf("%s matches nobody.\n", targetUuid.c_str());}
        }
        else if (command == "ls player") {
            printf("%d player(s) in the server :\n", level.players.size());
            for (pair<string, Player> player : level.players) {
                player.second.printPlayerInfo();
            }
        }
        else if (command == "ls bullet") {
            printf("%d bullet(s) in the server :\n", level.projectiles.size());
            for (pair<string, Projectile> projectile : level.projectiles) {
                projectile.second.printProjectileInfo();
            }
        }
        else if (command == "ls youkai") {
            printf("%d youkai(s) in the server :\n", level.youkais.size());
            for (pair<string, Youkai> youkai : level.youkais) {
                youkai.second.printYoukaiInfo();
            }
        }
        else if (command == "ls channel") {
            printf("%d channels(s) request to receive response :\n", clientChannels.size());
            for (int i = 0; i < clientChannels.size(); i++) {
                printf("id:%d; uuid:%s -> %s(isConnected:%d)\n",
                clientChannels[i]->id(), clientChannelsUuid[i].c_str(),
                clientChannels[i]->peeraddr().c_str(), clientChannels[i]->isConnected());
            }
        }
        else if (command == "ls item") {
            printf("%d item(s) in the server :\n", level.items.size());
            for (pair<string, Item> item : level.items) {
                item.second.printItemInfo();
            }
        }
        else if (command == "game start") {
            if (!level.gameStarted) {
                mtxLock.lock();
                level.gameStarted = true;
                level.leftTime = roundTime;
                printf("Game started! The match would last for %f second(s)\n", level.leftTime);
                level.resetAllPlayers(responseBuffer);
                level.resetLevelStatus(responseBuffer);
                sendGameLog("Host started the game.", responseBuffer);
                printf("The status of player(s) is reset.\n");
                mtxLock.unlock();
            }
            else {
                printf("The game is in progress.\n");
            }
        }
        else if (command == "game stop") {
            if (level.gameStarted) {
                mtxLock.lock();
                level.gameStarted = false;
                level.leftTime = 0.0f;
                level.resetLevelStatus(responseBuffer);
                printf("Game stopped!\n");
                mtxLock.unlock();
            }
            else {
                printf("The game has not been started.\n");
            }
        }
        else if (command == "game status") {
            mtxLock.lock();
            if (level.gameStarted) {
                printf("The game is in progress.\n");
                printf("The match would last for %f second(s).\n", level.leftTime);
                printf("%d player(s) on the server is still on contest:\n", level.players.size());

            }
            else {
                printf("The game has not been started.\n");
                printf("%d player(s) on the server is ready for the match.\n", level.players.size());
            }
            for (pair<string, Player> player : level.players) {
                printf("%s\n", player.second.playerName.c_str());
            }
            mtxLock.unlock();
        }
        else if (command == "game rank") {
            mtxLock.lock();
            if (level.gameStarted) {
                printf("%d player(s) on the server is still on contest:\n", level.players.size());
                for (pair<string, Player> player : level.players) {
                    printf("%s (Score : %d)\n", player.second.playerName.c_str(), player.second.score);
                }
            }
            else {
                printf("The game has not been started.\n");
            }
            mtxLock.unlock();
        }
        else if (command == "cirno") {
            printf("999!\n");
        }
        else if (command == "help my programming") {
            printf("Do yourself!\n");
        }
        else if (command == "exit") {
            printf("Use \"stop\" please.\n");
        }
        else if (command == "stop") {
            printf("Have a nice day!\n");
            break;
        }
        else if (command == "copyright") {
            printf("Copyright half_tree 2024\n");
            printf("Ver 1.0.0.0\n");
            printf("Also work as the program design class homework in NPU!\n");
        }
        else if (command == "license") {
            printf("GNU GENERAL PUBLIC LICENSE.\n Version 2, June 1991\n");
            printf("See more details in LICENSE file.\n");
        }
        else {
            printf("Invalid command : %s \n", command.c_str());
        }
    }
    printf("Server stopped.");
    exit(0);
}

void GameServer::startServer(string ip, int port, int maxPlayer) {
    webSocket.onopen = [this](const WebSocketChannelPtr& channel, const HttpRequestPtr& req) {
        printf("Game Client Connected: GET %s -> %s \n", req->Path().c_str(), req->client_addr.to_string().c_str());
        clientChannels.push_back(channel);
        clientChannelsUuid.push_back(req->Path());
        sendGameLog(req->client_addr.to_string() + " joined the server.", responseBuffer);
    };
    webSocket.onmessage = [this](const WebSocketChannelPtr& channel, const std::string& msg) {
        // printf("Game Client Json message: %.*s\n", (int)msg.size(), msg.data());
        string data = msg.data();
        LevelAction levelAction;
        levelAction.deserializeAction(data);
        pushToInteractBuffer(levelAction);
    };
    webSocket.onclose = [this](const WebSocketChannelPtr& channel) {
        deleteDeadChannels();
    };
    WebSocketServer server(&webSocket);
    server.setPort(port);
    server.setThreadNum(maxPlayer);
    printf("Starting server on %s\n", ip.c_str());
    printf("Server active. Listening on port %d\n", port);
    printf("Type \"help\" for more help.\n");
    server.run(ip.c_str(), 1);
    // 开启服务器
}

void GameServer::startHostGame(string ip, int port, int maxPlayer) {
    auto next_cycle = std::chrono::steady_clock::now();
    while (true) {
        // 计算下一周期的时间点
        next_cycle += std::chrono::milliseconds(static_cast<long>(rate * 1000.0f));
        // 执行关卡循环
        handleInteractBuffer();
        level.update(rate, mtxLock, responseBuffer);
        boardcastResponse();
        // 等待直到下一周期的时间点
        std::this_thread::sleep_until(next_cycle);
    }
}

void GameServer::pushToInteractBuffer(const LevelAction& levelAction) {
    mtxLock.lock();
    interactBuffer.push(levelAction);
    mtxLock.unlock();
}

void GameServer::handleInteractBuffer() {
    mtxLock.lock();
    while (!interactBuffer.empty()) {
        interactHandler.handleInteract(interactBuffer.front(), responseBuffer);
        interactBuffer.pop();
    }
    mtxLock.unlock();
}

void GameServer::boardcastResponse() {
    mtxLock.lock();
    while (!responseBuffer.empty()) {
        string responseMessage = responseBuffer.front().serializeAction();
        // printf("Server sent message : %s", responseMessage.c_str());
        for (const WebSocketChannelPtr& channelPtr : clientChannels) {
            if (channelPtr->isConnected()) {
                channelPtr->send(responseMessage);
            }
        }
        responseBuffer.pop();
    }
    mtxLock.unlock();
}

void GameServer::deleteDeadChannels() {
    mtxLock.lock();
    for (int i = 0; i < clientChannels.size(); i++) {
        if (!clientChannels[i]->isConnected()) {
            string delUuid = clientChannelsUuid[i].substr(1);
            string playerName = level.players[delUuid].playerName;
            printf("Player %s(uuid:%s) exited.\n",
                level.players[delUuid].playerName.c_str(), delUuid.c_str());
            if (level.players.find(delUuid) != level.players.end()) {
                level.players.erase(delUuid);
                pushToResponseBuffer("dp", vector<string> { delUuid }, responseBuffer);
                sendGameLog(playerName + " exited.", responseBuffer);
            }
            // 移除相应的玩家
            printf("%s disconnect from server.\n", clientChannels[i]->peeraddr().c_str());
            sendGameLog(clientChannels[i]->peeraddr() + " disconnected from server.", responseBuffer);
            clientChannels.erase(clientChannels.begin() + i);
            clientChannelsUuid.erase(clientChannelsUuid.begin() + i);
            // 移除指定的通信串口
            break;
        }
    }
    mtxLock.unlock();
}